home *** CD-ROM | disk | FTP | other *** search
/ Mac Easy 2010 May / Mac Life Ubuntu.iso / casper / filesystem.squashfs / usr / share / python-support / gnome-games-data / glchess / network.py < prev    next >
Encoding:
Python Source  |  2009-04-14  |  16.7 KB  |  550 lines

  1. # -*- coding: utf-8 -*-
  2. import os
  3. import socket
  4. import errno
  5. import gettext
  6.  
  7. import ggz
  8. import ui
  9. import game
  10.  
  11. from defaults import *
  12.  
  13. _ = gettext.gettext
  14.  
  15. class GGZServer:
  16.     def __init__(self, name):
  17.         self.name = name
  18.  
  19. class GGZLine:
  20.     TYPE_BLANK   = 'BLANK'
  21.     TYPE_COMMENT = 'COMMENT'
  22.     TYPE_SECTION = 'SECTION'
  23.     TYPE_FIELD   = 'FIELD'
  24.     TYPE_INVALID = 'INVALID'
  25.     
  26.     def __init__(self, text):
  27.         self.text = text
  28.         self.section = None
  29.         try:
  30.             self.decode(text)
  31.         except ValueError, e:
  32.             self.type = GGZLine.TYPE_INVALID
  33.             print e
  34.  
  35.     def decode(self, text):
  36.         content = text.strip()
  37.         if len(content) == 0:
  38.             self.type = GGZLine.TYPE_BLANK
  39.         elif content[0] == '#':
  40.             self.type = GGZLine.TYPE_COMMENT
  41.         elif content[0] == '[':
  42.             self.type = GGZLine.TYPE_SECTION
  43.             if content[-1] != ']':
  44.                 raise ValueError('Invalid section line: %s' % repr(content))
  45.             self.name = content[1:-1].strip()
  46.             self.value = None
  47.         else:
  48.             self.type = GGZLine.TYPE_FIELD
  49.             try:
  50.                 (name, value) = content.split('=', 1)
  51.             except ValueError:
  52.                 raise ValueError('Invalid field line: %s' % repr(content))
  53.             else:
  54.                 self.name = name.strip()
  55.                 self.value = value.strip()
  56.             
  57.     def setValue(self, value):
  58.         assert(self.type is GGZLine.TYPE_FIELD)
  59.         self.value = value
  60.         self.text = '%s=%s\n' % (self.name, value)
  61.  
  62. class GGZConfig:
  63.     
  64.     TYPE_NORMAL = 0
  65.     TYPE_GUEST  = 1
  66.     TYPE_FIRST  = 2
  67.     
  68.     def __init__(self):
  69.         try:
  70.             f = file(GGZ_CONFIG_FILE)
  71.             lines = f.readlines()
  72.         except IOError, e:
  73.             print 'Failed to load GGZ config: %s' % e.message
  74.             lines = []
  75.             
  76.         self.lines = []
  77.  
  78.         section = None
  79.         for text in lines:
  80.             line = GGZLine(text)
  81.             self.lines.append(line)
  82.  
  83.             if line.type is GGZLine.TYPE_SECTION:
  84.                 section = line.name
  85.                 line.section = section
  86.             else:
  87.                 line.section = section
  88.  
  89.     def getField(self, section, name):
  90.         for line in self.lines:
  91.             if line.section != section or line.type is not GGZLine.TYPE_FIELD:
  92.                 continue
  93.             if line.name == name:
  94.                 return line.value
  95.         raise KeyError('No field %s in section %s' % (name, section))
  96.     
  97.     def setField(self, section, name, value):
  98.         wasInSection = False
  99.         inSection = False
  100.         for (index, line) in enumerate(self.lines):
  101.             wasInSection = inSection
  102.             if line.section == section:
  103.                 inSection = True
  104.                 if line.type is GGZLine.TYPE_FIELD and line.name == name:
  105.                     line.setValue(value)
  106.                     return
  107.             elif wasInSection and not inSection:
  108.                 line = GGZLine('%s=%s\n' % (name, value))
  109.                 line.section = section
  110.                 self.lines.insert(index, line)
  111.                 return
  112.         
  113.         # New section            
  114.         if not wasInSection:
  115.             if len(self.lines) > 0:
  116.                 self.lines.append(GGZLine('\n'))
  117.             line = GGZLine('[%s]\n' % section)
  118.             line.section = section
  119.             self.lines.append(line)
  120.  
  121.         line = GGZLine('%s=%s\n' % (name, value))
  122.         line.section = section
  123.         self.lines.append(line)
  124.     
  125.     def removeSection(self, name):
  126.         keptLines = []
  127.         for line in self.lines:
  128.             if line.section != name:
  129.                 keptLines.append(line)
  130.         self.lines = keptLines
  131.         
  132.     def getServers(self):        
  133.         try:
  134.             value = self.getField('Servers', 'ProfileList')
  135.         except KeyError, e:
  136.             return []
  137.  
  138.         servers = []
  139.         for n in value.replace('\\ ', '\x00').split(' '):
  140.             name = n.replace('\x00', ' ')
  141.             try:
  142.                 server = self.getServer(name)
  143.             except KeyError, e:
  144.                 print e
  145.             else:
  146.                 servers.append(server)
  147.         return servers
  148.     
  149.     def getServer(self, name):
  150.         server = GGZServer(name)
  151.  
  152.         try:
  153.             server.host = self.getField(name, 'Host')
  154.             server.port = int(self.getField(name, 'Port'))
  155.             server.login = self.getField(name, 'Login')
  156.             server.loginType = int(self.getField(name, 'Type'))
  157.         except (KeyError, ValueError):
  158.             raise KeyError('Missing/invalid basic configuration for server %s' % repr(server.name))
  159.         
  160.         try:
  161.             server.password = self.getField(name, 'Password')
  162.         except KeyError:
  163.             server.password = None
  164.  
  165.         return server
  166.     
  167.     def updateServer(self, name, username, host, port):
  168.         self.setField(name, 'Host', host)
  169.         self.setField(name, 'Port', port)
  170.         self.setField(name, 'Login', username)
  171.         self.setField(name, 'Type', GGZConfig.TYPE_GUEST) # FIXME: Hardcoded
  172.         
  173.         try:
  174.             servers = self.getField('Servers', 'ProfileList')
  175.         except KeyError, e:
  176.             self.setField('Servers', 'ProfileList', name.replace(' ', '\\ '))
  177.         else:
  178.             self.setField('Servers', 'ProfileList', servers + ' ' + name.replace(' ', '\\ '))
  179.  
  180.         self.save()
  181.                 
  182.     def save(self):
  183.         lines = []
  184.         for line in self.lines:
  185.             lines.append(line.text)
  186.         try:
  187.             f = file(GGZ_CONFIG_FILE, 'w')
  188.             f.writelines(lines)
  189.         except IOError, e:
  190.             print 'Failed to save GGZ config: %s' % e.message
  191.  
  192. class GGZChannel(ggz.Channel):
  193.     def __init__(self, ui, feedback):
  194.         self.buffer = ''
  195.         self.ui = ui
  196.         self.feedback = feedback
  197.         self.socket = None
  198.         self.fd = -1
  199.         
  200.     def logXML(self, text):
  201.         self.logWindow.addText(text, 'input')
  202.  
  203.     def logBinary(self, data):
  204.         self.logWindow.addBinary(data, 'input')
  205.  
  206.     def connect(self, host, port):
  207.         self.close()
  208.         self.socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
  209.         self.fd = self.socket.fileno()
  210.         self.socket.setblocking(False)
  211.         self.ui.application.ioHandlers[self.fd] = self
  212.         self.ui.controller.watchFileDescriptor(self.fd)
  213.         
  214.         try:
  215.             self.socket.connect((host, port))
  216.         except socket.error, e:
  217.             # FIXME: Abort/retry if error
  218.             if e.args[0] == errno.EINPROGRESS:
  219.                 print 'connecting...'
  220.             else:
  221.                 print e
  222.                 
  223.         name = '%s:%d' % (host, port)
  224.         self.logWindow = self.ui.controller.addLogWindow(name, name, name)
  225.     
  226.     def send(self, data, isBinary = False):
  227.         if isBinary:
  228.             self.logWindow.addBinary(data, 'output')
  229.         else:
  230.             self.logWindow.addText(data, 'output')
  231.             
  232.         self.buffer += data
  233.         try:
  234.             nSent = self.socket.send(self.buffer)
  235.         except socket.error:
  236.             nSent = 0
  237.         self.buffer = self.buffer[nSent:]
  238.         
  239.     def close(self):
  240.         if self.socket is None:
  241.             return
  242.         self.ui.controller.unwatchFileDescriptor(self.fd)
  243.         self.socket.close()
  244.         self.socket = None
  245.         self.fd = -1
  246.         
  247.     def read(self):
  248.         try:
  249.             (data, address) = self.socket.recvfrom(65535)
  250.         except socket.error, e:
  251.             try:
  252.                 self.socket.close()
  253.             except socket.error:
  254.                 pass
  255.             self.feedback.closed(e.args[0])
  256.             return False
  257.         else:
  258.             if len(data) == 0:
  259.                 self.feedback.closed()
  260.                 return False
  261.             self.feedback.registerIncomingData(data)
  262.         return True
  263.  
  264. class GGZConnection(ggz.ClientFeedback):
  265.  
  266.     def __init__(self, dialog):
  267.         self.dialog = dialog
  268.         self.profile = dialog.profile
  269.         self.client = ggz.Client(self)
  270.         self.commands = []
  271.         self.sending = False
  272.         self.players = {}
  273.         self.actions = []
  274.         
  275.     def _getPlayerIcon(self, player):
  276.         try:
  277.             return {'guest':  'stock_person',
  278.                     'normal': 'stock_person',
  279.                     'admin':  'stock_person', # FIXME
  280.                     'bot':    'stock_notebook'}[player.type]
  281.         except KeyError:
  282.             return ''
  283.         
  284.     def _performAction(self, method, args):
  285.         if self.client.isBusy():
  286.             self.actions.insert(0, (method, args))
  287.         else:
  288.             method(*args)
  289.  
  290.     def close(self):
  291.         self.client.close('')
  292.  
  293.     def setBusy(self, isBusy):
  294.         self.dialog.controller.setBusy(isBusy)
  295.         
  296.         # Perform next queued action
  297.         if not isBusy and len(self.actions) > 0:
  298.             (method, args) = self.actions.pop()
  299.             method(*args)
  300.  
  301.     def onConnected(self):
  302.         self.dialog.controller.setSensitive(True)
  303.         self.dialog.controller.clearError()
  304.  
  305.     def onDisconnected(self, reason):
  306.         self.dialog.controller.setError(_('Disconnected from server'), reason)
  307.         self.dialog.controller.setSensitive(False)
  308.  
  309.     def openChannel(self, feedback):
  310.         print 'Open Channel'
  311.         self.setBusy(True)
  312.         socket = GGZChannel(self.dialog.ui, feedback)
  313.         socket.connect(self.profile.host, self.profile.port)
  314.         return socket
  315.  
  316.     def getLogin(self):
  317.         return (self.profile.login, self.profile.password)
  318.     
  319.     def getPassword(self, username):
  320.         self.client.setPassword(self.profile.password)
  321.         
  322.     def onMOTD(self, motd):
  323.         self.dialog.controller.addText(motd, 'motd')
  324.  
  325.     def roomAdded(self, room):
  326.         isChess = room.game is None or (room.game.protocol_engine == 'Chess' and room.game.protocol_version == '3')
  327.         if room.game is None:
  328.             protocol = None
  329.         else:
  330.             protocol = room.game.protocol_engine
  331.         self.dialog.controller.addRoom(int(room.id), room.name, room.nPlayers, room.description, room, protocol)
  332.  
  333.     def roomUpdated(self, room):
  334.         self.dialog.controller.updateRoom(room, room.nPlayers)
  335.  
  336.     def roomEntered(self, room):
  337.         self.room = room
  338.         self.dialog.controller.clearPlayers()
  339.         self.dialog.controller.clearTables()
  340.         
  341.         for player in self.client.players.itervalues():
  342.             self.dialog.controller.addPlayer(player, player.name, self._getPlayerIcon(player))
  343.         for table in self.client.tables.itervalues():
  344.             self.tableAdded(table)
  345.  
  346.         self.dialog.controller.enterRoom(room)
  347.  
  348.     def tableAdded(self, table):
  349.         self.tableUpdated(table)
  350.  
  351.     def tableUpdated(self, table):
  352.         if table.room != self.room:
  353.             return
  354.         description = table.description
  355.         if len(description) == 0:
  356.             description = _('No description')
  357.         nUsed = 0
  358.         for seat in table.seats:
  359.             if seat.type == 'bot' or seat.user != '':
  360.                 nUsed += 1
  361.  
  362.         # Can connect to Chess tables with free spaces
  363.         isChess = table.room.game is not None and table.room.game.protocol_engine == 'Chess'
  364.         canConnect = isChess and nUsed != len(table.seats)
  365.  
  366.         self.dialog.controller.updateTable(table, '%s' % table.id, '%i/%i' % (nUsed, len(table.seats)), description, canConnect)
  367.         for (i, seat) in enumerate(table.seats):
  368.             self.dialog.controller.updateSeat(table, i, seat.type, seat.user)
  369.  
  370.     def tableRemoved(self, table):
  371.         if table.room == self.room:
  372.             self.dialog.controller.removeTable(table)
  373.  
  374.     def onJoin(self, table, isSpectator, channel):
  375.         self.dialog.controller.joinTable(table)
  376.         g = GGZChess(self.dialog.game, channel)
  377.         return g
  378.  
  379.     def onLeave(self, reason):
  380.         print 'Leave table: %s' % reason
  381.         self.dialog.controller.joinTable(None)
  382.  
  383.     def playerAdded(self, player):
  384.         self.dialog.controller.addPlayer(player, player.name, self._getPlayerIcon(player))
  385.  
  386.     def playerRemoved(self, player):
  387.         self.dialog.controller.removePlayer(player)
  388.  
  389.     def onChat(self, chatType, sender, text):
  390.         self.dialog.controller.addText('\n%s: %s' % (sender, text), 'chat')
  391.  
  392. class GGZChess:
  393.     """
  394.     """
  395.     
  396.     def __init__(self, game, channel):
  397.         self.game = game
  398.         self.channel = channel
  399.         self.protocol = ggz.Chess(self)
  400.  
  401.     def registerIncomingData(self, data):
  402.         for o in data:
  403.             self.protocol.decode(o)
  404.  
  405.     def onSeat(self, seatNum, version):
  406.         self.seatNum = seatNum
  407.         print ('onSeat', seatNum, version)
  408.         
  409.     def seatIsFull(self, seatType):
  410.         return seatType == self.protocol.GGZ_SEAT_PLAYER or seatType == self.protocol.GGZ_SEAT_BOT
  411.  
  412.     def onPlayers(self, whiteType, whiteName, blackType, blackName):
  413.         print ('onPlayers', whiteType, whiteName, blackType, blackName)
  414.         self.whiteName = whiteName
  415.         self.blackName = blackName
  416.  
  417.     def onClockRequest(self):
  418.         print ('onTimeRequest',)
  419.         self.channel.send(self.protocol.sendClock(self.protocol.CLOCK_NONE, 0), True)
  420.     
  421.     def onClock(self, mode, seconds):
  422.         print ('onClock', mode, seconds)
  423.  
  424.     def onStart(self):
  425.         print ('onStart',)
  426.         # Create remote player
  427.         if self.seatNum == 0:
  428.             name = self.blackName
  429.         else:
  430.             name = self.whiteName
  431.         self.remotePlayer = game.ChessPlayer(name)
  432.         self.remotePlayer.onPlayerMoved = self.onPlayerMoved # FIXME: HACK HACK HACK!
  433.  
  434.         p = self.game.addHumanPlayer('Human')
  435.         if self.seatNum == 0:
  436.             self.game.setWhite(p)
  437.             self.game.setBlack(self.remotePlayer)
  438.         else:
  439.             self.game.setWhite(self.remotePlayer)
  440.             self.game.setBlack(p)
  441.  
  442.         self.game.start()
  443.  
  444.     def onPlayerMoved(self, player, move):
  445.         #FIXME: HACK HACK HACK!
  446.         if player is not self.remotePlayer:
  447.             self.channel.send(self.protocol.sendMove(move.canMove.upper()), True)
  448.  
  449.     def onMove(self, move):
  450.         print ('onMove', move)
  451.         # FIXME: Only remote players should be used
  452.         self.remotePlayer.move(move.lower())
  453.  
  454. class GGZNetworkDialog(ui.NetworkFeedback):
  455.     """
  456.     """
  457.     
  458.     def __init__(self, ui):
  459.         self.ui = ui
  460.         self.buffer = ''
  461.         self.profile = None
  462.         self.decoder = None
  463.         
  464.     def addProfile(self, profile):
  465.         """Called by ui.NetworkFeedback"""
  466.         (name, username, host, port) = profile
  467.  
  468.         # Make name unique
  469.         n = name
  470.         dupCount = 1
  471.         while True:
  472.             try:
  473.                 self.ui.ggzConfig.getServer(n)
  474.             except KeyError:
  475.                 break
  476.             else:
  477.                 dupCount += 1
  478.                 n = '%s (%d)' % (name, dupCount)
  479.         name = n
  480.         
  481.         self.ui.ggzConfig.updateServer(name, username, host, port)
  482.         
  483.         # FIXME: Return value is temporary
  484.         return self.ui.ggzConfig.getServer(name)
  485.  
  486.     def setProfile(self, profile):
  487.         """Called by ui.NetworkFeedback"""
  488.         if profile is None:
  489.             name = '(none)'
  490.         else:
  491.             name = profile.name
  492.         print 'Profile=%s' % name #FIXME Make proper log
  493.  
  494.         if self.decoder is not None:
  495.             self.decoder.close()
  496.         self.decoder = None
  497.  
  498.         self.controller.clearError()       
  499.         self.controller.setSensitive(False)
  500.         self.controller.clearRooms()
  501.         self.controller.clearPlayers()
  502.         self.controller.clearTables()
  503.         self.controller.clearText()
  504.         
  505.         self.profile = profile
  506.         if profile is None:
  507.             return
  508.        
  509.         self.decoder = GGZConnection(self)
  510.         self.decoder.client.start()
  511.     
  512.     def enterRoom(self, room):
  513.         """Called by ui.NetworkFeedback"""
  514.         print 'Entering room %s' % room.id
  515.         self.decoder._performAction(self.decoder.client.enterRoom, (room,))
  516.         
  517.     def _addGame(self):
  518.         self.game = self.ui.application.addGame('Network Game')
  519.         if self.game is None:
  520.             # FIXME: Notify user game aborted
  521.             return False
  522.         return True
  523.         
  524.     def startTable(self):
  525.         """Called by ui.NetworkFeedback"""
  526.         if self.decoder.room.game is None:
  527.             return
  528.         
  529.         if not self._addGame():
  530.             return
  531.         
  532.         print 'Starting table'
  533.         self.decoder.client.startTable(self.decoder.room.game.id, '', self.profile.login)
  534.  
  535.     def joinTable(self, table):
  536.         """Called by ui.NetworkFeedback"""
  537.         if not self._addGame():
  538.             return
  539.         
  540.         print 'Starting table %s' % table.id
  541.         self.decoder.client.joinTable(table)
  542.  
  543.     def leaveTable(self):
  544.         """Called by ui.NetworkFeedback"""
  545.         self.decoder.client.leaveTable()
  546.  
  547.     def sendChat(self, text):
  548.         """Called by ui.NetworkFeedback"""
  549.         self.decoder.client.sendChat(text)
  550.